/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jchy.renderit.core;

import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Image;

/**
 *
 * @author chyjm
 */
public class RenderEngine {

    private Camera camera;
    private Scene scene;
    private int xMin,  xMax,  yMin,  yMax;
    private BufferedImage image;

    public RenderEngine(Scene aScene, Camera aCamera, int width, int height) {

        camera = aCamera;
        scene = aScene;

        xMin = 0;
        xMax = width - 1;
        yMin = 0;
        yMax = height - 1;

        image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);

    }

    public Image getImage() {

        return image;

    }

    private Point getVrp(int x, int y) {

        Dir utvrpdir;

        Point result, utvrp;

        utvrpdir = new Dir();
        result = new Point();
        utvrp = new Point();

        utvrpdir.dx = camera.getInvZoom() * (x - 100);

        utvrpdir.dy = camera.getInvZoom() * (y - 100);
        utvrpdir.dz = 100.0;
        utvrpdir.normalize();

        utvrp.x = utvrpdir.dx;
        utvrp.y = utvrpdir.dy;
        utvrp.z = utvrpdir.dz;
        result = camera.getViewMat().transform(utvrp);

        return result;
    }

    public Hit getHit(int x, int y) {

        Hit result;
        double hitDist, minHitDist;
        Hit aHit;
        Point vrp, prp;

        result = new Hit();
        
        vrp = new Point();

        prp = camera.getPrp();

        vrp = getVrp(x, y);

        // Rectangular: also set utprp.x=utvrp.x; utprp.y=utvrp.y; transform;

        minHitDist = Double.MAX_VALUE;
        result.isHit = false;

        for (int scenectr = 0; scenectr < scene.getDisplayableCount(); scenectr++) {
            aHit = scene.getDisplayable(scenectr).intersect(prp, vrp);
            if (aHit.isHit) {
                aHit.setTarget(scene.getDisplayable(scenectr));
                hitDist = aHit.location.Dist(prp);
                if (hitDist < minHitDist) {
                    minHitDist = hitDist;
                    result = aHit.copy();
                }
            }

        } // for scenectr

        return result;
    }

    public void render() {
        // Set to 0.5 to make objects on screen 2x as large (in each direction)

        double cosAlpha, cosBeta;
        double phong, diffuse, ambient;
        float red, green, blue;
        Hit minHit;
        Point prp;
        Dir hitNormPos, hitNormal, viewDir;
        Dir lightDir, half;
        Light currLight;
        Graphics2D g;

        prp = new Point(0, 0, 0);
        lightDir = new Dir();
        half = new Dir();
        viewDir = new Dir();
        g = image.createGraphics();

        for (int x = xMin; x <= xMax; x = x + 1) {

            for (int y = yMin; y <= yMax; y = y + 1) {

                minHit = getHit(x, y);
                prp = camera.getPrp();
                if (minHit.isHit) {
                    viewDir = minHit.location.toDir(prp);
                    viewDir.normalize();

                    // Choose normal which faces the viewer
                    hitNormPos = minHit.location.toDir(minHit.normpt);
                    if (Dir.dot(hitNormPos, viewDir) > 0.0) {
                        hitNormal = minHit.location.toDir(minHit.normpt);
                    } else {
                        hitNormal = minHit.normpt.toDir(minHit.location);
                    }
                    hitNormal.normalize();
                    //System.out.println(hitNormal);


                    red = 0.0f;
                    green = 0.0f;
                    blue = 0.0f;
                    for (int lightctr = 0; lightctr < scene.getLightCount(); lightctr++) {
                        ambient = minHit.ambient / scene.getLightCount();

                        currLight = scene.getLight(lightctr);
                        lightDir = minHit.location.toDir(currLight.location);
                        lightDir.normalize();
                        cosAlpha = Dir.dot(hitNormal, lightDir);
                        if (cosAlpha < 0.0) {
                            cosAlpha = 0.0;
                        }

                        // Realtime Shadows
                        Hit blockHit = new Hit();
                        blockhit_block:
                        for (int blockctr = 0; blockctr < scene.getDisplayableCount(); blockctr++) {
                            blockHit = scene.getDisplayable(blockctr).intersect(minHit.location, currLight.location);
                            if (blockHit.isHit) {
                                break blockhit_block;
                            }
                        } // for blockctr

                        if ((blockHit.isHit) || (cosAlpha == 0.0)) {
                            phong = 0.0;
                            diffuse = 0.0;
                        } else {
                            half = Dir.add(lightDir, viewDir);
                            half.normalize();
                            //System.out.println("Half Vector: " + half);

                            cosBeta = Math.abs(Dir.dot(hitNormal, half));
                            diffuse = minHit.diffuse / scene.getLightCount() * cosAlpha;
                            phong = minHit.phongCoef / scene.getLightCount() * Math.pow(cosBeta, minHit.phongExp);

                        }

                        red += ambient * (minHit.color.getRed() / 256.0) + phong * (currLight.color.getRed() / 256.0) + diffuse * (currLight.color.getRed() / 256.0) * (minHit.color.getRed() / 256.0);
                        green += ambient * (minHit.color.getGreen() / 256.0) + phong * (currLight.color.getGreen() / 256.0) + diffuse * (currLight.color.getGreen() / 256.0) * (minHit.color.getGreen() / 256.0);
                        blue += ambient * (minHit.color.getBlue() / 256.0) + phong * (currLight.color.getBlue() / 256.0) + diffuse * (currLight.color.getBlue() / 256.0) * (minHit.color.getBlue() / 256.0);
                    }  // for each light in lights

                    g.setColor(new Color(red, green, blue));

                } // if minHit.isHit
                else {
                    g.setColor(Color.BLACK);
                }

                g.drawRect(x, y, 1, 1);

            } // for y
        } // for x
    }
}
